﻿
//=======================================================================
// <copyright file="TwitterObject.cs" company="Samuel Chen Studio">
//     Copyright (c) Samuel Chen Studio. All rights reserved.
//     author   : Samuel Chen
//     version  : 1.0
//     contact  : http://www.SamuelChen.net, samuel.net@gmail.com
// </copyright>
//=======================================================================

using System;
using System.Diagnostics;
using System.Xml;
using System.Reflection;
using System.Collections;
using System.Text;
using System.Globalization;

namespace Net.SamuelChen.SharpTweet {
    public class TwitterObject : IParsible{

        protected Hashtable Unparsed = new Hashtable();

        #region IParsible Members

        /// <summary>
        /// Parse a twitter object from given xml string. This will only affect fields. 
        /// The fields must has the same name with xml elements.
        /// </summary>
        /// <param name="content">The given twitter xml string.</param>
        public virtual void ParseXml(string content) {
            XmlDocument xml = new XmlDocument();
            try {
                xml.LoadXml(content);
            } catch (Exception ex) {
                Trace.TraceError(ex.ToString());
                throw ex;
            }

            TwitterObject o = this;
            Type t = GetType();
            Trace.TraceInformation("-------------- Begin parsing {0} -------------", t.ToString());

            try {
                XmlNode node = xml.DocumentElement.FirstChild;

                while (node != null) {

                    // Fetch the field info of the type
                    FieldInfo fi = t.GetField(node.Name);
                    Trace.TraceInformation("{0} - {1} - {2}", node.Name, fi, node.InnerText);

                    if (fi != null) {
                        // convert the value
                        object v = null;
                        try {
                            v = Convert.ChangeType(node.InnerText, fi.FieldType);
                        } catch (InvalidCastException ex) {
                            Trace.TraceWarning("{2}\n\tTry parsing {0} to unsupported type {1}. ", 
                                node.Name, fi.FieldType.ToString(), ex.Message);
                            v = ParseUnknownTypeXML(node, fi.FieldType);
                        } catch (FormatException ex) {
                            Trace.TraceWarning("{2}\n\tTry parsing invalid format of {0} to type {1}. ",
                                node.Name, fi.FieldType.ToString(), ex.Message);
                            v = ParseUnknownTypeXML(node, fi.FieldType);
                        }

                        fi.SetValue(o, v);
                    }

                    node = node.NextSibling;
                }

            } catch (Exception ex) {
                Trace.TraceError(ex.ToString());
                throw ex;
            }

            Trace.TraceInformation("-------------- End parsing {0} -------------", t.ToString());

            //TODO: Fire an OnParsed Event here.
        }

        /// <summary>
        /// Parse a twitter object from given JSON string. This will only affect fields. 
        /// The fields must has the same name with xml elements.
        /// </summary>
        /// <param name="content">The given twitter JSON string</param>
        public virtual void ParseJson(string content) {
            throw new NotImplementedException();
        }


        /// <summary>
        /// Parse unknown field type from XML node. This will only affect field. 
        /// The field must has the a name with xml element.
        /// </summary>
        protected virtual object ParseUnknownTypeXML(XmlNode node, Type tagetType) {
            if (null == node)
                return null;

            string name = node.Name;
            string content = node.InnerText;
            string t = tagetType.ToString().ToLower();
            if (t.Equals("system.boolean", StringComparison.CurrentCultureIgnoreCase)) {
                return false;   // It will be successfully parsed if its value is "true";
            } else if (t.Equals("system.datetime", StringComparison.CurrentCultureIgnoreCase)) {
                DateTime dt = Utility.ParseDateTime(content);
                return dt;
            } else
                Trace.TraceError("Can not convert \"{0}\" (value \"{1}\") to type \"{2}\"", node.Name, node.InnerText, tagetType);
            
            return null;
        }

        /// <summary>
        /// Parse unknown field type from JSON string. This will only affect field. 
        /// The field must has a same name with xml element.
        /// </summary>
        protected virtual object ParseUnknownTypeJSON(string name, string content, Type tagetType) {
            string t = tagetType.ToString().ToLower();
            if (t.Equals("system.boolean")) {
                return false;
            } else
                Trace.TraceWarning("Can not convert \"{0}\" (value \"{1}\") to type \"{2}\"", name, content, tagetType);
            return null;
        }
        #endregion


        public override string ToString() {
            StringBuilder sb = new StringBuilder();
            Type t = GetType();
            FieldInfo[] fis = t.GetFields();
            FieldInfo fi = null;

            sb.AppendLine(t.ToString());

            // loop to retreive names and values of fields.
            for (int i = 0; i < fis.Length; i++) {
                fi = fis[i];

                // retrieve value of current field.
                object v = string.Empty;
                try {
                    v = fi.GetValue(this);
                } catch (Exception ex) {
                    v = string.Format("Can not get value of {0} property. \n{1}", 
                        fi.Name, ex.Message);
                    Trace.TraceWarning("Can not get value of {0} property", fi.Name);
                }

                sb.AppendLine(string.Format("\t{0} [{2}] = {1}", fi.Name, v, fi.FieldType));
            }

            return sb.ToString();
        }

    }
}
